Passed
Pull Request — filestream (#169)
by
unknown
01:51
created

file-write.ts ➔ getTmpFilePathSync   A

Complexity

Conditions 1

Size

Total Lines 6
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 1
1
import {
2
    fsWritePromise,
3
    fillBufferAsync,
4
    fillBufferSync,
5
    processFileSync,
6
    processFileAsync,
7
    fsExistsPromise,
8
    fsWriteFilePromise,
9
    fsRenamePromise,
10
    unlinkIfExistSync,
11
    unlinkIfExist
12
} from "./util-file"
13
import * as fs from 'fs'
14
import { Header, findId3TagPosition, getId3TagSize } from "./id3-tag"
15
import { WriteCallback, WriteOptions } from "./types/write"
16
17
const MinBufferSize = Header.size
18
const DefaultFileBufferSize = 20 * 1024 * 1024
19
20
export function writeId3TagToFileSync(
21
    filepath: string,
22
    id3Tag: Buffer,
23
    options: WriteOptions
24
): void {
25
    if (!fs.existsSync(filepath)) {
26
        fs.writeFileSync(filepath, id3Tag)
27
        return
28
    }
29
    const tempFilepath = makeTempFilepath(filepath)
30
    processFileSync(filepath, 'r', (readFileDescriptor) => {
31
        try {
32
            processFileSync(tempFilepath, 'w', (writeFileDescriptor) => {
33
                fs.writeSync(writeFileDescriptor, id3Tag)
34
                copyFileWithoutId3TagSync(
35
                    readFileDescriptor,
36
                    writeFileDescriptor,
37
                    getFileBufferSize(options)
38
                )
39
            })
40
        } catch(error) {
41
            unlinkIfExistSync(tempFilepath)
42
            throw error
43
        }
44
    })
45
    fs.renameSync(tempFilepath, filepath)
46
}
47
48
export function writeId3TagToFile(
49
    filepath: string,
50
    id3Tag: Buffer,
51
    options: WriteOptions,
52
    callback: WriteCallback
53
): void {
54
    writeId3TagToFileAsync(filepath, id3Tag, options)
55
    .then(
56
        () => callback(null),
57
        (error) => callback(error)
58
    )
59
}
60
61
export async function writeId3TagToFileAsync(
62
    filepath: string,
63
    id3Tag: Buffer,
64
    options: WriteOptions
65
): Promise<void> {
66
    if (!await fsExistsPromise(filepath)) {
67
        await fsWriteFilePromise(filepath, id3Tag)
68
        return
69
    }
70
    const tempFilepath = makeTempFilepath(filepath)
71
    await processFileAsync(filepath, 'r', async (readFileDescriptor) => {
72
        try {
73
            await processFileAsync(tempFilepath, 'w',
74
                async (writeFileDescriptor) => {
75
                    await fsWritePromise(writeFileDescriptor, id3Tag)
76
                    await copyFileWithoutId3TagAsync(
77
                        readFileDescriptor,
78
                        writeFileDescriptor,
79
                        getFileBufferSize(options)
80
                    )
81
                }
82
            )
83
        } catch(error) {
84
            await unlinkIfExist(tempFilepath)
85
            throw error
86
        }
87
88
    })
89
    await fsRenamePromise(tempFilepath, filepath)
90
}
91
92
function getFileBufferSize(options: WriteOptions) {
93
    return Math.max(
94
        options.fileBufferSize ?? DefaultFileBufferSize,
95
        MinBufferSize
96
    )
97
}
98
function makeTempFilepath(filepath: string) {
99
    return `${filepath}.tmp-${Date.now()}`
100
}
101
102
function copyFileWithoutId3TagSync(
103
    readFileDescriptor: number,
104
    writeFileDescriptor: number,
105
    fileBufferSize: number
106
) {
107
    const buffer = Buffer.alloc(fileBufferSize)
108
    let readData
109
    while((readData = fillBufferSync(readFileDescriptor, buffer)).length) {
110
        const { data, bytesToSkip } = removeId3TagIfFound(readData)
111
        if (bytesToSkip) {
112
            fillBufferSync(readFileDescriptor, Buffer.alloc(bytesToSkip))
113
        }
114
        fs.writeSync(writeFileDescriptor, data, 0, data.length, null)
115
    }
116
}
117
118
async function copyFileWithoutId3TagAsync(
119
    readFileDescriptor: number,
120
    writeFileDescriptor: number,
121
    fileBufferSize: number
122
) {
123
    const buffer = Buffer.alloc(fileBufferSize)
124
    let readData
125
    while((readData = await fillBufferAsync(readFileDescriptor, buffer)).length) {
126
        const { data, bytesToSkip } = removeId3TagIfFound(readData)
127
        if (bytesToSkip) {
128
            await fillBufferAsync(readFileDescriptor, Buffer.alloc(bytesToSkip))
129
        }
130
        await fsWritePromise(writeFileDescriptor, data, 0, data.length, null)
131
    }
132
}
133
134
function removeId3TagIfFound(data: Buffer) {
135
    const id3TagPosition = findId3TagPosition(data)
136
    if (id3TagPosition === -1) {
137
        return { data }
138
    }
139
    const dataFromId3Start = data.subarray(id3TagPosition)
140
    const id3TagSize = getId3TagSize(dataFromId3Start)
141
    return {
142
        data: Buffer.concat([
143
            data.subarray(0, id3TagPosition),
144
            dataFromId3Start.subarray(Math.min(id3TagSize, dataFromId3Start.length))
145
        ]),
146
        bytesToSkip: Math.max(0, id3TagSize - dataFromId3Start.length)
147
    }
148
}
149